package common

import (
	"bufio"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	rand1 "crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/smtp"
	"os"
	"strings"
	"time"

	"strconv"

	"github.com/pkg/errors"
	"github.com/snluu/uuid"
	"golang.org/x/text/encoding/simplifiedchinese"
)

const apiurl string = "http://127.0.0.1:5000/generate"
const weburl string = "https://goeasy.io/goeasy/publish"

type Charset string

const (
	UTF8    = Charset("UTF-8")
	GB18030 = Charset("GB18030")
)

func ConvertByte2String(byte []byte, charset Charset) string {

	var str string
	switch charset {
	case GB18030:
		var decodeBytes, _ = simplifiedchinese.GB18030.NewDecoder().Bytes(byte)
		str = string(decodeBytes)
	case UTF8:
		fallthrough
	default:
		str = string(byte)
	}

	return str
}

type Classmsg struct {
	ID   int    `json:"id"`
	Text string `json:"text"`
}
type Config struct {
	NumOfExp       int  `json:"numofexp"`
	Myrange        int  `json:"myrange"`
	MaxNumOfOper   int  `json:"maxnumofoper"`
	HasFraction    bool `json:"hasfraction"`
	HasMulAndDiv   bool `json:"hasmulanddiv"`
	HasParentheses bool `json:"hasparentheses"`
}
type Expresstion struct {
	Answer     string `json:"answer"`
	ExpressStr string `json:"expression"`
}
type Result struct {
	Questions []Expresstion `json:"questions"`
	Success   bool          `json:"success"`
}

func String_2_Bool(str string) bool {
	b, e := strconv.ParseBool(str)
	if e != nil {
		b = false
	}
	return b
}
func String_2_int(str string) int {
	b, e := strconv.Atoi(str)
	if e != nil {
		b = 2
	}
	return b
}

func DeleteFile(examid int, create time.Time) error {
	now := create.String()
	fmt.Println(now)
	eid := strconv.Itoa(examid)
	split := strings.Split(now, " ")
	time := strings.Split(split[0], "-")
	filepath := fmt.Sprintf("%s/%s/%s/%s/", "static", "exams", time[0], time[1], time[2])
	filename := fmt.Sprintf("%s.json", eid)
	err := os.RemoveAll(filepath + filename)
	if err != nil {
		panic("delete file failed!")
	}
	//nn, err := file.WriteString(string(content))
	return err
}

func WriteFile(examid int, content []byte) error {
	now := time.Now().String()
	eid := strconv.Itoa(examid)
	split := strings.Split(now, " ")
	time := strings.Split(split[0], "-")
	filepath := fmt.Sprintf("%s/%s/%s/%s/%s/", "static", "exams", time[0], time[1], time[2])
	filename := fmt.Sprintf("%s.json", eid)
	err := os.MkdirAll(filepath, 0777)
	file, err := os.Create(filepath + filename)
	if err != nil {
		panic("write file failed!")
	}
	defer file.Close()
	//nn, err := file.WriteString(string(content))
	nn, err := file.Write(content)
	fmt.Println(nn)
	return err
}
func ReadFile(filename string) {
	file, err := os.Open(filename)
	if err != nil {
		panic("open failed!")
	}
	defer file.Close()
	b := bufio.NewReader(file)
	line, err := b.ReadString('\n')
	for ; err == nil; line, err = b.ReadString('\n') {
		fmt.Print(line)
	}
	if err == io.EOF {
		//fmt.Println("test")
		fmt.Print(line)
	} else {
		panic("read occur error")
	}

}

func GetExpresstions(con *Config) (*Result, error) {
	jsonStr, err := json.Marshal(con)
	fmt.Printf("%s", jsonStr)
	if err != nil {
		return nil, errors.New("配置参数有误")
	}
	fmt.Println(apiurl)
	resp, err := http.Post(apiurl, "application/json;charset=utf-8", bytes.NewBuffer(jsonStr))
	//resp, err := http.NewRequest("POST", apiurl, bytes.NewBuffer(jsonStr))

	if err != nil {
		return nil, errors.New("网络出现异常")
	}
	defer resp.Body.Close()
	var body []byte
	body, err = ioutil.ReadAll(resp.Body)
	fmt.Printf("%s", body)
	if err == nil {
		var result Result
		err = json.Unmarshal(body, &result)
		return &result, err
	}
	return nil, err
}

func Publish(msg string) error {
	pubmsg := fmt.Sprintf("%s%s", "appkey=BC-a68a3da95aa044bc9e73530936261a27&channel=calc&content=", msg)
	resp, err := http.Post(weburl, "application/x-www-from-urlencoded;charset=utf-8",
		strings.NewReader(pubmsg))
	//resp, err := http.NewRequest("POST", apiurl, bytes.NewBuffer(jsonStr))

	if err != nil {
		return errors.New("网络出现异常")
	}
	defer resp.Body.Close()
	var body []byte
	body, err = ioutil.ReadAll(resp.Body)
	fmt.Println(string(body))
	return err
}

/**
* size 随机码的位数
* kind 0    // 纯数字
       1    // 小写字母
       2    // 大写字母
       3    // 数字、大小写字母
*/
func Krand(size int, kind int, classid uint) []byte {
	ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size)
	is_all := kind > 2 || kind < 0
	rand.Seed(time.Now().UnixNano() + int64(classid))
	for i := 0; i < size; i++ {
		if is_all { // random ikind
			ikind = rand.Intn(3)
		}
		scope, base := kinds[ikind][0], kinds[ikind][1]
		result[i] = uint8(base + rand.Intn(scope))
	}
	return result
}

// 计算字符串的md5值
func Md5(source string) string {
	md5h := md5.New()
	md5h.Write([]byte(source))
	return hex.EncodeToString(md5h.Sum(nil))
}

func Truncate(s string, n int) string {
	runes := []rune(s)
	if len(runes) > n {
		return string(runes[:n])
	}
	return s
}

func UUID() string {
	return uuid.Rand().Hex()
}

func GetCurrentTime() time.Time {
	loc, _ := time.LoadLocation("Asia/Shanghai")
	return time.Now().In(loc)
}

func SendToMail(user, password, host, to, subject, body, mailtype string) error {
	hp := strings.Split(host, ":")
	auth := smtp.PlainAuth("", user, password, hp[0])
	var content_type string
	if mailtype == "html" {
		content_type = "Content-Type: text/" + mailtype + "; charset=UTF-8"
	} else {
		content_type = "Content-Type: text/plain" + "; charset=UTF-8"
	}
	msg := []byte("To: " + to + "\r\nFrom: " + user + "\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
	send_to := strings.Split(to, ";")
	return smtp.SendMail(host, auth, user, send_to, msg)
}

func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func Decrypt(ciphertext []byte, keystring string) ([]byte, error) {
	// Key
	key := []byte(keystring)

	// Create the AES cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	// Before even testing the decryption,
	// if the text is too small, then it is incorrect
	if len(ciphertext) < aes.BlockSize {
		err = errors.New("Text is too short")
		return nil, nil
	}

	// Get the 16 byte IV
	iv := ciphertext[:aes.BlockSize]

	// Remove the IV from the ciphertext
	ciphertext = ciphertext[aes.BlockSize:]

	// Return a decrypted stream
	stream := cipher.NewCFBDecrypter(block, iv)

	// Decrypt bytes from ciphertext
	stream.XORKeyStream(ciphertext, ciphertext)

	return ciphertext, nil
}

func Encrypt(plaintext []byte, keystring string) ([]byte, error) {

	// Key
	key := []byte(keystring)

	// Create the AES cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	// Empty array of 16 + plaintext length
	// Include the IV at the beginning
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))

	// Slice of first 16 bytes
	iv := ciphertext[:aes.BlockSize]

	// Write 16 rand bytes to fill iv
	if _, err := io.ReadFull(rand1.Reader, iv); err != nil {
		return nil, err
	}

	// Return an encrypted stream
	stream := cipher.NewCFBEncrypter(block, iv)

	// Encrypt bytes from plaintext to ciphertext
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	return ciphertext, nil
}
